/*
 * WPA Supplicant - Helper functions for scan result processing
 * Copyright (c) 2007-2008, Jouni Malinen <j@w1.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Alternatively, this software may be distributed under the terms of BSD
 * license.
 *
 * See README and COPYING for more details.
 */

#include "includes.h"

#include "common.h"
#include "drivers/driver.h"
#include "ieee802_11_defs.h"


const u8 * wpa_scan_get_ie(const struct wpa_scan_res *res, u8 ie)
{
        const u8 *end, *pos;

        pos = (const u8 *) (res + 1);
        end = pos + res->ie_len;

        while (pos + 1 < end) {
                if (pos + 2 + pos[1] > end)
                        break;
                if (pos[0] == ie)
                        return pos;
                pos += 2 + pos[1];
        }

        return NULL;
}


const u8 * wpa_scan_get_vendor_ie(const struct wpa_scan_res *res,
                                  u32 vendor_type)
{
        const u8 *end, *pos;

        pos = (const u8 *) (res + 1);
        end = pos + res->ie_len;

        while (pos + 1 < end) {
                if (pos + 2 + pos[1] > end)
                        break;
                if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 &&
                    vendor_type == WPA_GET_BE32(&pos[2]))
                        return pos;
                pos += 2 + pos[1];
        }

        return NULL;
}


struct wpabuf * wpa_scan_get_vendor_ie_multi(const struct wpa_scan_res *res,
                                             u32 vendor_type)
{
        struct wpabuf *buf;
        const u8 *end, *pos;

        buf = wpabuf_alloc(res->ie_len);
        if (buf == NULL)
                return NULL;

        pos = (const u8 *) (res + 1);
        end = pos + res->ie_len;

        while (pos + 1 < end) {
                if (pos + 2 + pos[1] > end)
                        break;
                if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 &&
                    vendor_type == WPA_GET_BE32(&pos[2]))
                        wpabuf_put_data(buf, pos + 2 + 4, pos[1] - 4);
                pos += 2 + pos[1];
        }

        if (wpabuf_len(buf) == 0) {
                wpabuf_free(buf);
                buf = NULL;
        }

        return buf;
}


int wpa_scan_get_max_rate(const struct wpa_scan_res *res)
{
        int rate = 0;
        const u8 *ie;
        int i;

        ie = wpa_scan_get_ie(res, WLAN_EID_SUPP_RATES);
        for (i = 0; ie && i < ie[1]; i++) {
                if ((ie[i + 2] & 0x7f) > rate)
                        rate = ie[i + 2] & 0x7f;
        }

        ie = wpa_scan_get_ie(res, WLAN_EID_EXT_SUPP_RATES);
        for (i = 0; ie && i < ie[1]; i++) {
                if ((ie[i + 2] & 0x7f) > rate)
                        rate = ie[i + 2] & 0x7f;
        }

        return rate;
}


void wpa_scan_results_free(struct wpa_scan_results *res)
{
        size_t i;

        if (res == NULL)
                return;

        for (i = 0; i < res->num; i++)
                os_free(res->res[i]);
        os_free(res->res);
        os_free(res);
}


/* Compare function for sorting scan results. Return >0 if @b is considered
 * better. */
static int wpa_scan_result_compar(const void *a, const void *b)
{
        struct wpa_scan_res **_wa = (void *) a;
        struct wpa_scan_res **_wb = (void *) b;
        struct wpa_scan_res *wa = *_wa;
        struct wpa_scan_res *wb = *_wb;
        int wpa_a, wpa_b, maxrate_a, maxrate_b;

        /* WPA/WPA2 support preferred */
        wpa_a = wpa_scan_get_vendor_ie(wa, WPA_IE_VENDOR_TYPE) != NULL ||
                wpa_scan_get_ie(wa, WLAN_EID_RSN) != NULL;
        wpa_b = wpa_scan_get_vendor_ie(wb, WPA_IE_VENDOR_TYPE) != NULL ||
                wpa_scan_get_ie(wb, WLAN_EID_RSN) != NULL;

        if (wpa_b && !wpa_a)
                return 1;
        if (!wpa_b && wpa_a)
                return -1;

        /* privacy support preferred */
        if ((wa->caps & IEEE80211_CAP_PRIVACY) == 0 &&
            (wb->caps & IEEE80211_CAP_PRIVACY))
                return 1;
        if ((wa->caps & IEEE80211_CAP_PRIVACY) &&
            (wb->caps & IEEE80211_CAP_PRIVACY) == 0)
                return -1;

        /* best/max rate preferred if signal level close enough XXX */
        if ((wa->level && wb->level && abs(wb->level - wa->level) < 5) ||
            (wa->qual && wb->qual && abs(wb->qual - wa->qual) < 10)) {
                maxrate_a = wpa_scan_get_max_rate(wa);
                maxrate_b = wpa_scan_get_max_rate(wb);
                if (maxrate_a != maxrate_b)
                        return maxrate_b - maxrate_a;
        }

        /* use freq for channel preference */

        /* all things being equal, use signal level; if signal levels are
         * identical, use quality values since some drivers may only report
         * that value and leave the signal level zero */
        if (wb->level == wa->level)
                return wb->qual - wa->qual;
        return -(wb->level - wa->level);
}


void wpa_scan_sort_results(struct wpa_scan_results *res)
{
        qsort(res->res, res->num, sizeof(struct wpa_scan_res *),
              wpa_scan_result_compar);
}
